/* -*- C++ -*- */

//=============================================================================
/*
* @author Feloweria <380374483@qq.com>
* Copyright (C) Feloweria
*
* $ID 2014-05-14 11:25:19 AM
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* @version 1.0
*/
//=============================================================================


#include "Network/Socket.h"
#include "Network/SocketThread.h"

#ifdef __LINUX__
	#include <sys/socket.h>
	#include <netdb.h>
	#include <unistd.h>
#endif



FELOWRIA_BEGIN

Socket::Socket()
:sock(-1),recv_buffer(recv_block, recv_block + SOCKET_RECV_SIZE), send_buffer(send_block, send_block + SOCKET_SEND_SIZE),
port(0), connected(false), disconnected(false), thread(NULL)
{
#ifdef __WINDOWS__
	read_event.socket_buffer = &recv_buffer;
	read_event.type = SOCKET_EVENT_READ;

	write_event.socket_buffer = &send_buffer;
	write_event.type = SOCKET_EVENT_WRITE;
#endif
}

Socket::~Socket()
{

}

bool Socket::Create()
{
	
#ifdef __LINUX__
	sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
#endif 

#ifdef __WINDOWS__
	sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED);
#endif

	if (sock == -1)
	{
		LOG_ERROR("Socket::Create socket create fail");
		return false;
	}

#ifdef __WINDOWS__
	u_long arg = 1;
	ioctlsocket(sock, FIONBIO, &arg);
#endif

	return true;
}

bool Socket::Connect(const char *addr, int32 port)
{
	struct hostent * host = gethostbyname(addr);
	if (!host)
		return false;
	
	sockaddr_in sock_addr;
	sock_addr.sin_family = host->h_addrtype;
	sock_addr.sin_port = ntohs((uint16)port);
	memcpy(&sock_addr.sin_addr.s_addr, host->h_addr_list[0], host->h_length);

	if (connect(sock, (sockaddr*)&sock_addr, sizeof(sockaddr_in)))
		return false;

	return true;
}

void Socket::Disconnect()
{
	if(disconnected)
		return ;

	disconnected = true;

	if(thread)
		thread->UnRegister(this);
	thread = NULL;
#ifdef __LINUX__
	shutdown(sock, SHUT_RDWR);
	close(sock);
#endif

#ifdef __WINDOWS__
	shutdown(sock, SD_BOTH);
	closesocket(sock);
#endif

	OnDisconnect();
}
void Socket::OnAccept(SocketThread *thread,int64 sock,const char *ip,int32 port)
{
	this->thread = thread;
	this->sock = sock;
	this->ip = ip;
	this->port = port;
	this->connected = true;

	thread->Register(this);
	Read();
	OnConnect();
}

void Socket::Read()
{
#ifdef __LINUX__
	char *buf = NULL;
	int32 len = 0;
	int32 size = 0;
	while(true)
	{
		recv_buffer.Writable(buf,len);
		
		if(len <= 0)
		{
			Disconnect();
			break;
		}


		size = recv(sock, buf, len, MSG_DONTWAIT);

		if(0 == size)
		{
			Disconnect();
			break;
		}

		if(-1 == size)
		{
			if(errno == EAGAIN || errno == EWOULDBLOCK)
				break;

			Disconnect();
			break;
		}

		if(!recv_buffer.WriteSkip(size))
			Disconnect();
	}
#endif

#ifdef __WINDOWS__
	char *buf = NULL;
	int32 len = 0;
	DWORD flags = 0;

	WSABUF wsa_buf;
	recv_buffer.Writable(buf, len);

	if (len <= 0)
	{
		Disconnect();
		return ;
	}
	
	wsa_buf.buf = buf;
	wsa_buf.len = len;
	memset(&read_event.overlap, 0, sizeof(read_event.overlap));

	if (WSARecv(sock, &wsa_buf, 1, NULL, &flags, &read_event.overlap, NULL))
	{
		if (WSAGetLastError() != WSA_IO_PENDING)
			Disconnect();
	}

#endif
}

void Socket::Send()
{
#ifdef __LINUX__
	bool result = false;	
	if (send_lock.compare_exchange_weak(result, true))
	{
		char *buf = NULL;
		int32 len = 0;
		int32 size = 0;

		while(true)
		{
			send_buffer.Readable(buf,len);
		
			if(len <= 0)
				break;

			int32 size = send(sock, buf, len, MSG_DONTWAIT);
			
			if(-1 == size)
			{
				if(errno == EAGAIN || errno == EWOULDBLOCK)
					break;
				
				Disconnect();
				break;
			}

			send_buffer.ReadSkip(size);
		}

		send_lock = false;
	}
#endif

#ifdef __WINDOWS__
	bool result = false;
	if (send_lock.compare_exchange_weak(result, true))
	{
		char *buf = NULL;
		int32 len = 0;
		int32 size = 0;

		WSABUF wsa_buf;
	
		send_buffer.Readable(buf, len);

		if (len <= 0)
			return ;

		wsa_buf.buf = buf;
		wsa_buf.len = len;
		memset(&write_event.overlap, 0, sizeof(write_event.overlap));

		if (WSASend(sock, &wsa_buf, 1, NULL, 0, &write_event.overlap, 0))
		{
			if (WSAGetLastError() != WSA_IO_PENDING)
				Disconnect();

		}
		

		send_lock = false;
	}
#endif
}

void Socket::Reset()
{
	sock = -1;
	recv_buffer.Reset();
	send_buffer.Reset();
	ip = "";
	port = 0;
	connected = false;
	disconnected = false;
	send_lock = false;
	thread = NULL;
}

void Socket::Release()
{
	connected = false;
}

FELOWRIA_END
